package com.newjiabo.applet.modules.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.newjiabo.ski.domain.SkiConnect;
import com.newjiabo.ski.domain.dto.WechatMPLoginParams;
import com.newjiabo.ski.mapper.SkiConnectMapper;
import com.newjiabo.applet.modules.entity.dto.AuthToken;
import com.newjiabo.applet.modules.entity.dto.ConnectAuthUser;
import com.newjiabo.applet.modules.entity.enums.ClientTypeEnum;
import com.newjiabo.applet.modules.entity.enums.ConnectEnum;
import com.newjiabo.applet.modules.entity.enums.SourceEnum;
import com.newjiabo.applet.modules.service.ConnectService;
import com.newjiabo.applet.modules.service.MemberService;
import com.newjiabo.common.cache.CachePrefix;
import com.newjiabo.common.core.domain.entity.SkiMember;
import com.newjiabo.common.core.domain.model.LoginUser;
import com.newjiabo.common.core.enums.ResultCode;
import com.newjiabo.common.core.enums.UserEnums;
import com.newjiabo.common.core.redis.RedisCache;
import com.newjiabo.common.exception.ServiceException;
import com.newjiabo.common.utils.DateUtils;
import com.newjiabo.common.ski.config.WeChatConfig;
import com.newjiabo.framework.web.service.TokenService;
import com.newjiabo.ski.service.ISkiMemberService;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.naming.NoPermissionException;
import java.nio.charset.StandardCharsets;
import java.security.AlgorithmParameters;
import java.security.Security;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * {类内容}
 *
 * @author CaoMengHui.
 * @version 1.0
 * @since 2023/9/15 9:54
 */
@Service
public class ConnectServiceImpl implements ConnectService {

    private static final Logger log = LoggerFactory.getLogger(ConnectServiceImpl.class);
    @Resource
    private RedisCache redisCache;
    @Resource
    private MemberService memberService;
    @Resource
    private TokenService tokenService;
    @Resource
    private SkiConnectMapper connectMapper;
    @Resource
    private ISkiMemberService skiMemberService;

    @Override
    public String miniProgramAutoLogin(WechatMPLoginParams params) {
        Object cacheData = redisCache.getCacheObject(CachePrefix.WECHAT_SESSION_PARAMS.getPrefix() + params.getUuid());
        Map<String, String> map = new HashMap<>(3);
        if (ObjectUtil.isNull(cacheData) || CollectionUtil.isEmpty((Map<?, ?>) cacheData)) {
            // 得到微信小程序联合登录信息
            JSONObject json = this.getConnect(params.getCode());
            log.info("通过微信api返回：{}",json.toString());
            //存储session key 后续登录用得到
            map.put("sessionKey", json.getStr("session_key"));
            map.put("unionId", json.getStr("unionid"));
            map.put("openId", json.getStr("openid"));
            redisCache.setCacheObject(CachePrefix.WECHAT_SESSION_PARAMS.getPrefix() + params.getUuid(), map, 900, TimeUnit.SECONDS);
        } else {
            map = (Map<String, String>) cacheData;
        }
        //微信联合登陆参数
        return phoneMpBindAndLogin(map.get("sessionKey"), params, map.get("openId"), map.get("unionId"));
    }

    /**
     * 手机号绑定登录
     *
     * @param sessionKey 微信sessionKey
     * @param params     微信小程序自动登录参数
     * @param openId     微信openid
     * @param unionId    微信unionid
     * @return
     * @author CaoMengHui.
     * @date 2023/9/15 10:45
     */
    private String phoneMpBindAndLogin(String sessionKey, WechatMPLoginParams params, String openId, String unionId) {
        try {
            String encryptedData = params.getEncryptedData();
            String iv = params.getIv();
            // TODO <CaoMengHui>: 换一种处理方式
            JSONObject userInfo = this.getUserInfo(encryptedData, sessionKey, iv);
            log.info("联合登陆返回：{}", userInfo.toString());
            ConnectAuthUser connectAuthUser = new ConnectAuthUser();
            connectAuthUser.setUuid(openId);
            connectAuthUser.setNickname(params.getNickName());
            connectAuthUser.setAvatar(params.getImage());
            if (userInfo.containsKey("purePhoneNumber")) {
                String phone = (String) userInfo.get("purePhoneNumber");
                connectAuthUser.setUsername("m" + phone);
                connectAuthUser.setPhone(phone);
            } else {
                connectAuthUser.setUsername(UUID.randomUUID().toString());
            }
            connectAuthUser.setSource(ConnectEnum.WECHAT);
            connectAuthUser.setType(ClientTypeEnum.WECHAT_MP);
            AuthToken authToken = new AuthToken();
            authToken.setUnionId(unionId);
            connectAuthUser.setToken(authToken);
            return this.unionLoginCallback(connectAuthUser, true);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 第三方联合登陆
     * 1.判断是否使用开放平台
     * 1.1如果使用开放平台则使用UnionId进行登录
     * 1.2如果不适用开放平台则使用OpenId进行登录
     * <p>
     * 2.用户登录后判断绑定OpenId
     *
     * @param authUser 第三方登录封装类
     * @param longTerm 是否长时间有效
     * @return token
     * @throws NoPermissionException 不允许操作
     */
    private String unionLoginCallback(ConnectAuthUser authUser, boolean longTerm) {
        try {
            SkiMember skiMember = null;
            //判断是否传递手机号，如果传递手机号则使用手机号登录
            if (StrUtil.isNotBlank(authUser.getPhone())) {
                skiMember = memberService.findByMobile(authUser.getPhone());
            }
            //如果未查到手机号的会员则使用第三方登录
            if (skiMember == null) {
                SkiConnect connect = null;
                //使用UnionId登录
                if (StrUtil.isNotBlank(authUser.getToken().getUnionId())) {
                    connect = this.getByUnIdAndUnType(authUser.getToken().getUnionId(), authUser.getSource().name());
                } else {
                    //使用OpenID登录
                    SourceEnum sourceEnum = SourceEnum.getSourceEnum(authUser.getSource(), authUser.getType());
                    connect = this.getByUnIdAndUnType(authUser.getUuid(), sourceEnum.name());
                }
                //查询绑定关系
                if (connect == null) {
                    skiMember = memberService.autoRegister(authUser);
                } else {
                    //查询会员
                    skiMember = skiMemberService.selectSkiMemberById(connect.getUserId());
                    //如果未绑定会员，则把刚才查询到的联合登录表数据删除
                    if (skiMember == null) {
                        this.delByUnIdAndUnType(connect.getUnionId(), connect.getUnionType());
                        skiMember = memberService.autoRegister(authUser);
                    }
                }
            }
            LoginUser loginUser = new LoginUser();
            loginUser.setUserEnums(UserEnums.MEMBER);
            loginUser.setMemberId(skiMember.getId());
            loginUser.setMember(skiMember);
            return tokenService.createToken(loginUser, UserEnums.MEMBER, longTerm);
        } catch (Exception e) {
            log.error("联合登陆失败：", e);
            throw e;
        }
    }

    private Boolean delByUnIdAndUnType(String unionId, String unionType) {
        connectMapper.deleteByUnionIdAndUnionType(unionId, unionType);
        return true;
    }


    private SkiConnect getByUnIdAndUnType(String uuid, String sourceEnumName) {
        return connectMapper.getByUnIdAndUnType(uuid, sourceEnumName);
    }

    /**
     * 解密，获取微信信息
     *
     * @param encryptedData 加密信息
     * @param sessionKey    微信sessionKey
     * @param iv            微信揭秘参数
     * @return 用户信息
     */
    public JSONObject getUserInfo(String encryptedData, String sessionKey, String iv) {
        log.info("encryptedData:{},sessionKey:{},iv:{}", encryptedData, sessionKey, iv);
        //被加密的数据
        byte[] dataByte = Base64.getDecoder().decode(encryptedData);
        //加密秘钥
        byte[] keyByte = Base64.getDecoder().decode(sessionKey);
        //偏移量
        byte[] ivByte = Base64.getDecoder().decode(iv);
        try {
            //如果密钥不足16位，那么就补足.  这个if 中的内容很重要
            int base = 16;
            if (keyByte.length % base != 0) {
                int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
                byte[] temp = new byte[groups * base];
                Arrays.fill(temp, (byte) 0);
                System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
                keyByte = temp;
            }
            //初始化
            Security.addProvider(new BouncyCastleProvider());
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
            SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
            AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
            parameters.init(new IvParameterSpec(ivByte));
            //初始化
            cipher.init(Cipher.DECRYPT_MODE, spec, parameters);
            byte[] resultByte = cipher.doFinal(dataByte);
            if (null != resultByte && resultByte.length > 0) {
                String result = new String(resultByte, StandardCharsets.UTF_8);
                return JSONUtil.parseObj(result);
            }
        } catch (Exception e) {
            log.error("解密，获取微信信息错误", e);
        }
        throw new ServiceException(ResultCode.USER_CONNECT_ERROR);
    }

    /**
     * 通过微信返回等code 获取openid 等信息
     *
     * @param code 微信code
     * @return 微信返回的信息
     */
    public JSONObject getConnect(String code) {
        String url = "https://api.weixin.qq.com/sns/jscode2session?" +
                "appid=" + WeChatConfig.getAppid() + "&" +
                "secret=" + WeChatConfig.getSecret() + "&" +
                "js_code=" + code + "&" +
                "grant_type=authorization_code";
        String content = HttpUtil.get(url);
        log.error(content);
        return JSONUtil.parseObj(content);
    }

    @Autowired
    private SkiConnectMapper skiConnectMapper;

    /**
     * 查询联合登陆
     *
     * @param id 联合登陆主键
     * @return 联合登陆
     */
    @Override
    public SkiConnect selectSkiConnectById(Long id) {
        return skiConnectMapper.selectSkiConnectById(id);
    }

    /**
     * 查询联合登陆列表
     *
     * @param skiConnect 联合登陆
     * @return 联合登陆
     */
    @Override
    public List<SkiConnect> selectSkiConnectList(SkiConnect skiConnect) {
        return skiConnectMapper.selectSkiConnectList(skiConnect);
    }

    /**
     * 新增联合登陆
     *
     * @param skiConnect 联合登陆
     * @return 结果
     */
    @Override
    public int insertSkiConnect(SkiConnect skiConnect) {
        skiConnect.setCreateTime(DateUtils.getNowDate());
        return skiConnectMapper.insertSkiConnect(skiConnect);
    }

    /**
     * 修改联合登陆
     *
     * @param skiConnect 联合登陆
     * @return 结果
     */
    @Override
    public int updateSkiConnect(SkiConnect skiConnect) {
        return skiConnectMapper.updateSkiConnect(skiConnect);
    }

    /**
     * 批量删除联合登陆
     *
     * @param ids 需要删除的联合登陆主键
     * @return 结果
     */
    @Override
    public int deleteSkiConnectByIds(Long[] ids) {
        return skiConnectMapper.deleteSkiConnectByIds(ids);
    }

    /**
     * 删除联合登陆信息
     *
     * @param id 联合登陆主键
     * @return 结果
     */
    @Override
    public int deleteSkiConnectById(Long id) {
        return skiConnectMapper.deleteSkiConnectById(id);
    }

    @Override
    public SkiConnect selectByMemberId(String memberId) {
        return skiConnectMapper.selectByMemberId(memberId);
    }
}
